import pygame, time
from pygame.locals import *
import pygame.locals as pygamelocals
import random
from script_objects import *

pygame.font.init()

from constants import *

from gb_exceptions import *




def run_game(game):
    """run a general game
    - a bit messy, some time I will make it more OO
    """

    myscreen = Screen((800,600),game.title)


    def get_game_screen(name,game,scriptvars,screen,saved={},scriptglobals={}):
        if saved.has_key(name):
            return saved[name]
        else:
            return RunGameScreen(screen,game,game.gamescreens[name],scriptvars,scriptglobals)

    scriptvar = scriptgame(game)
    pygame.init()

    # Run Game init code to import modules etc.
    scriptglobals={}
    # TODO:
    # From Release 0.2 onward we can remove the except - left in to
    # retain compatibility with previous 0.1.x releases' games
    try:
        if not game.initcode == "":
            exec game.initcode in {}, scriptglobals
    except: pass

    
    #Check if a gamescreen is already loaded, otherwise return a new Rungamescreen object
    savedgamescreens = {}
    try:
        rungamescreen = get_game_screen(game.startscreen, game, \
                                        scriptvar, myscreen, savedgamescreens,scriptglobals=scriptglobals)
    
    except gb_runtime_exception, inst:
        pygame.quit()
        raise inst
    except Exception,inst:
        pygame.quit()
        raise gb_scripting_exception(log="Error Finding Initial GameScreen - " \
                    + str(inst) ,orig_except = inst)

    currentgamescreen = game.startscreen

    # By default use 25 fps
    framerate = 25

    # Test the different clocks
    clock = pygame.time.Clock()
    # Give access to the frame per second counter
    scriptvar.get_fps = clock.get_fps

    starttime = time.time()
    clock.tick(framerate)
    timetaken = time.time() - starttime

    # Default (bad) way
    def timer_generator(fps,clock):
        lasttime=time.time()
        framedelay = 1./fps
        while 1:
            now = time.time()
            time_taken = now - lasttime
            if time_taken >= framedelay:
                lasttime = now
                clock.tick()
                yield 
            else:
                time.sleep(framedelay - time_taken)
                lasttime = time.time()
                clock.tick()
                yield 


    timer_generator_inst = timer_generator(framerate,clock)
    timerfunction = lambda x: timer_generator_inst.next()

    if (0.93 * timetaken) < (1./framerate) < (1.07 * timetaken):
        # Use the SDL timer
        timerfunction = clock.tick
        print "using sdl timer"
    else:
        print "using python timer"

    while 1:
        try:
            rungamescreen.gameloop(clock) # buffers for the next frame, but doesn't draw to the screen
            timerfunction(framerate)
            # Now draw it to the screen
            rungamescreen.render()

        # Catch events that have to be processed at this level
        except game_event,inst:
            try:
                command = inst.instruction[0]
                params = inst.instruction[1:]
                if command == "changeGameScreen":
                    gsname = params[0]
                    savestate = params[1]
                    if savestate:
                        savedgamescreens[currentgamescreen] = rungamescreen
                    else:
                        del savedgamescreens[currentgamescreen]
                    currentgamescreen = gsname
                    rungamescreen = get_game_screen(gsname,game,scriptvar,myscreen,savedgamescreens,scriptglobals=scriptglobals)
            except Exception,inst:
                pygame.quit()
                raise gb_runtime_exception(log="Error Changing Gamescreen",orig_except=inst)
        except exit_game:
            break

        except gb_scripting_exception:
            pygame.quit()
            raise
        except gb_runtime_exception:
            pygame.quit()
            raise

        except Exception, inst:
            print inst
            pygame.quit()
            raise gb_runtime_exception(log=str(inst),orig_except=inst)


    pygame.quit()




class Screen(object):
    """"Screen/window Class for rendering onto."""
    def __init__(self,size,name):
        self.surface = pygame.display.set_mode(size)
        pygame.mouse.set_visible(1)
        self.surface.fill((255,255,255))
        pygame.display.set_caption(name)
        self.resolution = size

    def clear(self,color=(255,255,255)):
        self.surface.fill(color)

    def render(self):
        pygame.display.update()

class RunGameScreen(object):
    """"The Class that represents an individual gamescreen (or level etc) during execution"""
    def __init__(self,screen,game,gamescreen,scriptgamevar,scriptglobals):
        self.game = game
        self.gamescreen = gamescreen # the game.gamescreen object
        self.screen = screen 
        self.surface = pygame.Surface(self.gamescreen.dimensions)
        self.scriptglobals = scriptglobals
        self.scriptgamevar = scriptgamevar # "game" object for scripting

        # Cache for workstate events (compiled)
        self.__workstate_event_cache={}

        self.spriteobjects = {} # Loaded (runtime) sprite instances
        for go in gamescreen.startobjects:
            if go.sprite is not None:
                if not self.spriteobjects.has_key(go.sprite):
                    self.spriteobjects[go.sprite] = RunSprite(self.game.sprites[go.sprite])

        # for items removed in scripts
        self.to_delete_scriptgameobjs = []

        self.time_since_last_call = 0

        # Order is important:
        # 1
        self.gameobjects = {}

        # 2
        self.scriptgamescreenvar = scriptgamescreen(self)
        
        # 3
        # HAVE TO CHANGE THIS - how to name objects?
        for a in gamescreen.startobjects:
            self.gameobjects[a.name] = RunGameObject(self,a)

    def get_workstate_event_code(self,workstate,event):


        cache = self.__workstate_event_cache

        wscache = cache.setdefault(workstate,{})
        event_code = wscache.get(event,None)
        if event_code is None:
            workstates = self.game.workstates            
            ws = workstates[workstate]
            event_code = ws.actions.get(event,"")

            # Walk the inheritance tree and evaluate the method.
            while event_code == "" and ws.inherits is not None:
                ws = workstates.get(ws.inherits)
                event_code = ws.actions.get(event,"")

            if not event_code == "":
                event_code = compile(event_code,str(workstate) + ":<string>","exec")
            wscache[event]=event_code

        return event_code

    def add_object_instance(self,obj,name):

        # If we're removing sprites when they're not used we need to
        # split this
        self.gameobjects[name] = RunGameObject(self,self.game.gameobjects[obj])

        return self.gameobjects[name]


    def remove_object_instance(self,name):

        del self.gameobjects[name]





    def gameloop(self,clock):
        """Main Game Loop - called each itteration"""

        #self.input() 
        self.itteration()
        # Draw this gamescreen on to our window.
        self.frame_buffer()

        # Get time since last clock cycle
        self.time_since_last_call = clock.get_time()

        events = pygame.event.get()
        game_objects = self.gameobjects.values()
        for event in events:
            if event.type == QUIT:
                # Simple solution
                raise exit_game()
            elif event.type == KEYDOWN:
                for gameobj in game_objects:
                    gameobj._do_event(EVENT_KEYDOWN)
                    gameobj._do_event(EVENT_KEYDOWN*event.key)
                pass
            elif event.type == KEYUP:
                for gameobj in game_objects:
                    gameobj._do_event(EVENT_KEYUP)
                    gameobj._do_event(EVENT_KEYUP*event.key)
                pass
            elif event.type == MOUSEBUTTONUP:
                (x,y) = pygame.mouse.get_pos()
                x = x - self.scriptgamescreenvar.x
                y = y - self.scriptgamescreenvar.y
                for gameobj in game_objects:
                    if gameobj.check_point_collision(x,y):
                        if event.button == 1:
                                gameobj._do_event(EVENT_LMOUSEUP)
                        elif event.button == 2:
                                gameobj._do_event(EVENT_RMOUSEUP)


        # Get time since last clock cycle
        # Do this again in case a long time has elapsed
        self.time_since_last_call = clock.get_time()

        # Check timers for each object
        now = time.time()
        for go in game_objects:
            timers = go.scriptvar.timers

            if len(timers) > 0:
                while len(timers) > 0:
                    if timers[0][0] < now:
                        t,msg = timers.pop(0)
                        go._do_event(EVENT_TIMER,extraparams = dict(timermsg = msg) )
                    else: break

        for sgo in self.to_delete_scriptgameobjs:
            objs = [n for n in self.gameobjects.keys() if self.gameobjects[n].scriptvar == sgo]
            for n in objs:
                del self.gameobjects[n]
            
    def render(self):
        # blit the window on to the monitor.
        self.screen.render()

    def frame_buffer(self):
        """render a frame"""
        # Draw background - to be replaced with options (background image, colour etc)
        try:
            self.screen.clear(self.scriptgamescreenvar.color)
        except :
            self.screen.clear()


        # Draw objects - blit only part on screen
        swidth,sheight = self.screen.resolution
        gswidth,gsheight = self.gamescreen.dimensions

        #------------------------------------------#
        # NB: use the .z attribute of the game objects
        # to order the rendering.
        #------------------------------------------#

        gameobjs = {}
        gos = self.gameobjects.values()
        for i in range(len(gos)):            
            gameobjs.setdefault(gos[i].scriptvar.z,[ ]).append(gos[i])

        zvals = gameobjs.keys()[:]
        zvals.sort()


        screen_x = self.scriptgamescreenvar.x
        screen_y = self.scriptgamescreenvar.y


        screensurf = self.screen.surface

        for i in zvals:
            objs = gameobjs[i]
            for item in objs:
                item_x = item.scriptvar.x
                item_y = item.scriptvar.y

                if item_x - screen_x + item.surface.get_width() < 0 or  \
                    item_x - screen_x > swidth or \
                    item_y - screen_y + item.surface.get_height()< 0 or \
                    item_y - screen_y  > sheight:

                    item._do_event(EVENT_OFFSCREEN)
                else:
                    screensurf.blit(item.surface,(item_x - \
                    screen_x,item_y - screen_y))


    def input(self):
        """Completely not done yet"""
        pass


    def itteration(self):
        """Called every itteration"""
        for item in self.gameobjects.values():
            # standard stuff (e.g. sprite frames)
            item._dostep()
            # non-standard iteration handler
            item._do_event(EVENT_ITERATION)
        pass




class RunGameObject(object):
    """"The Class that represents a GameObject while the game is being executed"""
    def __init__(self,gamescreenobj,GameObj):

        # Object for scripting
        self.scriptvar = scriptgameobj(self,gamescreenobj)
        # Set up sprite
        scriptvar = self.scriptvar
        scriptvar.sprite = GameObj.sprite
        scriptvar.frame = 0
        scriptvar.__loopcounter = 0
        scriptvar.workstate = GameObj.baseworkstate

        # set x and y coordinates
        try:
            scriptvar.x = GameObj.x
            scriptvar.y = GameObj.y
        except: pass

        self.oldworkstate = GameObj.baseworkstate

        self.game = gamescreenobj.game
        self.gamescreenobj = gamescreenobj
        self.GameObject = GameObj
        self.surface = pygame.Surface((10,10))

        # call the init event
        try:
            self._do_event(EVENT_INIT)

        except gb_runtime_exception:
            raise

        except Exception,inst:
            raise gb_scripting_exception(log = "Error Running Initialisation Code for object",orig_except=inst,event="1")




    def _do_event(self,event,scriptlocals = None,extraparams=None):
        """ call a scripted event. 
        Scriptlocals should normally be None
        exrtaparameters is a dict of any extra parameters for the call 
        (e.g. for timer event)
        """

        """
        workstates = self.game.workstates
        ws = self.game.workstates[self.scriptvar.workstate]
        event_code = ws.actions.get(event,"")

        # Walk the inheritance tree and evaluate the method.
        while event_code == "" and ws.inherits is not None:
            ws = workstates.get(ws.inherits)
            event_code = ws.actions.get(event,"")

        """
        
        if scriptlocals is None:
            scriptlocals = {"self":self.scriptvar,"game": self.gamescreenobj.scriptgamevar, \
                            "gamescreen":self.gamescreenobj.scriptgamescreenvar, \
                            "random":random,"constants":pygamelocals}

        if extraparams is not None:
            for k in extraparams.keys():
                scriptlocals[k] = extraparams[k]

        try:
            event_code = self.gamescreenobj.get_workstate_event_code(self.scriptvar.workstate,event)

            if event_code == "":
                return

            exec event_code in self.gamescreenobj.scriptglobals, scriptlocals
        except gb_scripting_exception,inst:
            if inst.event == "Unknown":
                inst.event = event
            if inst.workstate is None:
                inst.workstate = self.scriptvar.workstate
            raise inst
        except gb_runtime_exception,inst:
            raise gb_scripting_exception(log="Error in User Script",orig_except=inst,event=event,workstate=self.scriptvar.workstate)

        except Exception,inst:
            raise gb_scripting_exception(log="Error in User Script",orig_except=inst,event=event,workstate=self.scriptvar.workstate)
        


        

    def check_point_collision(self,x,y):
        """Checks if a point is within the object's collision area"""
        (width,height) = self.surface.get_size()
        return self.scriptvar.x <= x and self.scriptvar.y <= y and self.scriptvar.x + width >= x and self.scriptvar.y + height >= y

    def drawsprite(self,sprite,x,y,frame = None):
        """draws a sprite on the object at position x,y """

        spr = self.gamescreenobj.spriteobjects.get(sprite,None)
        if spr is None:
            # Load sprite
            self.gamescreenobj.spriteobjects[sprite] =  \
                    RunSprite(self.gamescreenobj.game.sprites[sprite])
            spr = self.gamescreenobj.spriteobjects[sprite]

        if frame is None:
            self.surface.blit(spr.frames[self.scriptvar.frame],(x,y))
            return
        else:
            self.surface.blit(spr.frames[frame],(x,y))


    def drawtext(self,text,size=None,colour=None):
        """ Using "Colour" since that is the correct English spelling ;-)
        Also not using defaults so we can overload the function
        to allow different calling conventions later"""

        if not type(text) == type(""):
            text = str(text)
        if size is None:
            size = 12
        if colour is None:
            colour = (0,0,0)

        # Create a font
        font = pygame.font.Font(None, size)

        # Render the text
        text = font.render(text, True, colour)#, background colour

        # Create a rectangle
        textRect = text.get_rect()

        # Center the rectangle
        textRect.centerx = self.surface.get_rect().centerx
        textRect.centery = self.surface.get_rect().centery

        # Blit the text
        self.surface.blit(text, textRect)

    def clear(self):
        if self.scriptvar.sprite is None:
            self.surface.fill(self.scriptvar.color)

    def resize(self,res,height=None):
        if not height is None:
            res = (res,height)
        self.surface = pygame.transform.scale(self.surface,res)
        self.scriptvar.width = res[0]
        self.scriptvar.height = res[1]

    def _dostep(self):
        scriptvar = self.scriptvar
        # Check if we have changed workstate:

        if not self.oldworkstate == scriptvar.workstate:
            self._do_event(EVENT_INIT)
            self.oldworkstate = scriptvar.workstate
        if not scriptvar.sprite is None:
            spr = self.gamescreenobj.spriteobjects.get(scriptvar.sprite,None)
            if spr is None:
                # Load sprite
                self.gamescreenobj.spriteobjects[scriptvar.sprite] =  \
                        RunSprite(self.gamescreenobj.game.sprites[scriptvar.sprite])
                spr = self.gamescreenobj.spriteobjects.get(scriptvar.sprite)

            self.surface.set_colorkey(spr.alphakey)
            framerate = spr.framerate

            scriptvar.__loopcounter = (scriptvar.__loopcounter + 1)  \
                                                % (framerate*len(spr.frames))

            scriptvar.frame = (scriptvar.__loopcounter ) / framerate

            self.surface.blit(spr.frames[scriptvar.frame], (0,0))


class RunSprite(object):
    """"The Class that represents a Sprite while the game is being executed"""
    def __init__(self,sprite):
        self.sprite = sprite
        self.framerate = sprite.framerate
        self.frames = []

        # Set transparent colour based on sprite instance
        self.alphakey = sprite.alphakey

        # Now load the raw images into surfaces
        temp = {}
        for im in sprite.imagefiles:
            if not temp.has_key(im["filename"]):
                # TODO - if the image has per-pixel alpha then it should use convert_alpha()
                surface = pygame.image.load(im["filename"]).convert()

                temp[im["filename"]] = surface
                
        # And save sub-surfaces for each frame (effectively a pointer to pixelarray, so memory efficient)
        for im in sprite.imagefiles:
            x1,y1 = im["topleft"]
            x2,y2 = im["bottomright"]
            rec = pygame.Rect( x1 , y1 , x2 - x1 , y2 - y1 )
            self.frames.append(temp[im["filename"]].subsurface(rec))

